﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Input;
using System.Windows.Media;
using DependencyAnalysis.DataModel;
using DependencyAnalysis.Internal;
using theeg.DependencyAnalysis.CommonAddin.Behaviors;
using theeg.GraphWpf.Arrangement;
using theeg.GraphWpf.Models;

namespace theeg.DependencyAnalysis.CommonAddin.Models
{
    public class ArchitectureGraphModel : ViewModelBase
    {
        private const string DisplayClassIdentifier = "+<>c__DisplayClass";
        private bool _showProjectOnly = true;

        public bool ShowProjectOnly
        {
            get { return _showProjectOnly; }
            set
            {
                SetNotifyingField(ref _showProjectOnly, value, "ShowProjectOnly");
                BuildGraph();
            }
        }

        private bool _isHorizontal = true;

        public bool IsHorizontal
        {
            get { return _isHorizontal; }
            set
            {
                SetNotifyingField(ref _isHorizontal, value, "IsHorizontal");
                BuildGraph();
            }
        }

        private bool _hideTransitiveEdges;

        public bool HideTransitiveEdges
        {
            get { return _hideTransitiveEdges; }
            set
            {
                SetNotifyingField(ref _hideTransitiveEdges, value, "HideTransitiveEdges");
                BuildGraph();
            }
        }

        private bool _compactClassView;

        public bool CompactClassView
        {
            get { return _compactClassView; }
            set
            {
                SetNotifyingField(ref _compactClassView, value, "CompactClassView");
                BuildGraph();
            }
        }

        private IDependencyModel _dependencyModel;

        public IDependencyModel DependencyModel
        {
            get { return _dependencyModel; }
            set
            {
                if (Equals(value, _dependencyModel)) return;
                _dependencyModel = value;
                BuildGraph();
                RaisePropertyChanged("DependencyModel");
            }
        }

        private GraphParameter _graphContent;

        public GraphParameter GraphContent
        {
            get { return _graphContent; }
            set { SetNotifyingField(ref _graphContent, value, "GraphContent"); }
        }

        private readonly ObservableCollection<IDescriptor> _hiddenNodes = new ObservableCollection<IDescriptor>();
        private Dictionary<string, Node> _discoveredNodes;
        private Dictionary<string, Transition> _discoveredTransitions;

        public ObservableCollection<IDescriptor> HiddenNodes
        {
            get { return _hiddenNodes; }
        }

        public ICommand HideNodesCommand { get; private set; }

        public ICommand UncoverNodesCommand { get; private set; }

        public ArchitectureGraphModel()
        {
            HideNodesCommand = new RelayCommand(HideNodesExecuted);
            UncoverNodesCommand = new RelayCommand(UncoverNodesExecuted);
        }

        private void HideNodesExecuted(object o)
        {
            var descriptors = o as IEnumerable<IDescriptor>;
            if (descriptors != null)
            {
                foreach (var descriptor in descriptors)
                {
                    HiddenNodes.Add(descriptor);
                }
                BuildGraph();
            }
        }

        private void UncoverNodesExecuted(object o)
        {
            var descriptor = o as IDescriptor;
            if (descriptor != null)
            {
                if (HiddenNodes.Contains(descriptor))
                {
                    HiddenNodes.Remove(descriptor);
                }
            }
            else
            {
                HiddenNodes.Clear();
            }
            BuildGraph();
        }

        private void BuildGraph()
        {
            if (_dependencyModel == null) return;
            var parameter = new GraphParameter();
            parameter.HorizontalLayout = IsHorizontal;
            parameter.HideTransitiveEdges = HideTransitiveEdges;

            var expandedStates = new Dictionary<string, bool>();
            if (_discoveredNodes != null)
            {
                foreach (var node in _discoveredNodes)
                {
                    var subGraph = node.Value as SubGraph;
                    if (subGraph == null)
                    {
                        continue;
                    }
                    expandedStates[node.Key] = subGraph.IsExpanded;
                }
            }

            _discoveredNodes = new Dictionary<string, Node>();
            _discoveredTransitions = new Dictionary<string, Transition>();
            bool limitToProject = _showProjectOnly;
            var dependenciesPerAssembly = new Dictionary<ModuleDescriptor, ISet<DependencyDescriptor>>();
            foreach (var pair in (from x in _dependencyModel.DependenciesPerModule
                                  where !limitToProject || ((ModuleDescriptor)x.Key).IsPartOfProject
                                  select x))
            {
                dependenciesPerAssembly.Add((ModuleDescriptor)pair.Key, pair.Value);
            }
            BuildNodes(dependenciesPerAssembly, limitToProject, _discoveredNodes, parameter);

            BuildModuleTransitions(dependenciesPerAssembly, _discoveredNodes, _discoveredTransitions, parameter);

            BuildSubmoduleTransitions(_discoveredNodes, _discoveredTransitions);

            BuildTypeTransitions(_discoveredNodes, _discoveredTransitions);

            if (!CompactClassView)
            {
                BuildMemberTransitions(_discoveredNodes, _discoveredTransitions);
            }

            CalculateTransitionCardinality(_discoveredTransitions);

            foreach (var node in _discoveredNodes)
            {
                var subGraph = node.Value as SubGraph;
                if (subGraph == null)
                {
                    continue;
                }
                bool isExpanded;
                expandedStates.TryGetValue(node.Key, out isExpanded);
                subGraph.IsExpanded = isExpanded;
            }

            GraphContent = parameter;
        }

        private void CalculateTransitionCardinality(Dictionary<string, Transition> transitions)
        {
            foreach (var transition in transitions.Values)
            {
                transition.TargetText = transition.GenericInt + "";
                transition.Magnitude = Math.Log(transition.GenericInt + 1, 6) * 2;
            }
        }

        private void BuildMemberTransitions(Dictionary<string, Node> discoveredNodes, Dictionary<string, Transition> discoveredTransitions)
        {
            var dependenciesPerMember = new Dictionary<IDescriptor, ISet<DependencyDescriptor>>();
            foreach (var pair in (from x in _dependencyModel.DependenciesPerMember
                                  where (((MemberDescriptor)x.Key).ImplementingType.Submodule.Module).IsPartOfProject
                                  select x))
            {
                dependenciesPerMember.Add(pair.Key, pair.Value);
            }
            foreach (MemberDescriptor member1 in dependenciesPerMember.Keys)
            {
                var member = GetDeclaringMember(member1, dependenciesPerMember);

                if (!discoveredNodes.ContainsKey("Type " + member.ImplementingType.Identifier))
                {
                    continue;
                }
                var typeGraph = (SubGraph)discoveredNodes["Type " + member.ImplementingType.Identifier];
                var targetKey = "Member " + member.Identifier;
                if (!discoveredNodes.ContainsKey(targetKey))
                {
                    continue;
                }
                var target = discoveredNodes[targetKey];
                foreach (var dependency in dependenciesPerMember[member])
                {
                    var sourceMember = GetDeclaringMember(dependency.Source, dependenciesPerMember);
                    if (!Equals(sourceMember.ImplementingType, member.ImplementingType)) continue;
                    var sourceKey = "Member " + sourceMember.Identifier;
                    if (sourceKey == targetKey) continue;
                    if (!discoveredNodes.ContainsKey(sourceKey))
                    {
                        continue;
                    }
                    var source = discoveredNodes[sourceKey];
                    if (discoveredTransitions.ContainsKey(sourceKey + " -> " + targetKey))
                    {
                        discoveredTransitions[sourceKey + " -> " + targetKey].GenericInt++;
                        continue;
                    }
                    var transition = GraphBuilder.BuildTransition(source, target);
                    transition.Brush = new SolidColorBrush(Colors.DimGray);
                    transition.GenericInt = 1;
                    transition.TransitionStyle = TransitionStyle.Bezier;
                    transition.RoutingStyle = IsHorizontal ? RoutingStyle.FreeHandHorizontal : RoutingStyle.FreeHandVertical;
                    typeGraph.Transitions.Add(transition);
                    discoveredTransitions.Add(sourceKey + " -> " + targetKey, transition);
                }
            }
        }

        private static MemberDescriptor GetDeclaringMember(MemberDescriptor member, IDictionary<IDescriptor, ISet<DependencyDescriptor>> dependenciesPerMember)
        {
            var visitedParents = new HashSet<MemberDescriptor>();
            var name = member.Name;
            while ((name.StartsWith(DisplayClassIdentifier) || name.StartsWith("<") || name.StartsWith("CS$<>"))
                && !visitedParents.Contains(member))
            {
                visitedParents.Add(member);
                var declaringMethod = dependenciesPerMember[member].FirstOrDefault(x => Equals(x.Source.Parent, member.Parent));
                if (declaringMethod != null)
                {
                    member = declaringMethod.Source;
                }
                else
                {
                    break;
                }
            }
            return member;
        }

        private void BuildTypeTransitions(Dictionary<string, Node> discoveredNodes, Dictionary<string, Transition> discoveredTransitions)
        {
            var dependenciesPerType = new Dictionary<TypeDescriptor, ISet<DependencyDescriptor>>();
            foreach (var pair in (from x in _dependencyModel.DependenciesPerType
                                  where (((TypeDescriptor)x.Key).Submodule.Module).IsPartOfProject
                                  select x))
            {
                dependenciesPerType.Add((TypeDescriptor)pair.Key, pair.Value);
            }
            foreach (var type in dependenciesPerType.Keys)
            {
                if (!discoveredNodes.ContainsKey("Submodule " + type.Submodule.Identifier))
                {
                    continue;
                }
                var submoduleGraph = (SubGraph)discoveredNodes["Submodule " + type.Submodule.Identifier];
                var targetKey = "Type " + type.Identifier;
                if (!discoveredNodes.ContainsKey(targetKey))
                {
                    continue;
                }
                var target = discoveredNodes[targetKey];
                foreach (var dependency in dependenciesPerType[type])
                {
                    var sourceType = dependency.Source.ImplementingType;
                    if (!Equals(sourceType.Submodule, type.Submodule)) continue;
                    var sourceKey = "Type " + sourceType.Identifier;
                    if (sourceKey == targetKey) continue;
                    if (!discoveredNodes.ContainsKey(sourceKey))
                    {
                        continue;
                    }
                    var source = discoveredNodes[sourceKey];
                    if (discoveredTransitions.ContainsKey(sourceKey + " -> " + targetKey))
                    {
                        discoveredTransitions[sourceKey + " -> " + targetKey].GenericInt++;
                        continue;
                    }
                    var transition = GraphBuilder.BuildTransition(source, target);
                    transition.Brush = new SolidColorBrush(Colors.DimGray);
                    transition.TransitionStyle = TransitionStyle.Bezier;
                    transition.GenericInt = 1;
                    transition.RoutingStyle = IsHorizontal && !CompactClassView ? RoutingStyle.FreeHandHorizontal : RoutingStyle.FreeHandVertical;
                    submoduleGraph.Transitions.Add(transition);
                    discoveredTransitions.Add(sourceKey + " -> " + targetKey, transition);
                }
            }
        }

        private void BuildSubmoduleTransitions(Dictionary<string, Node> discoveredNodes, Dictionary<string, Transition> discoveredTransitions)
        {
            var dependenciesPerSubmodule = new Dictionary<SubmoduleDescriptor, ISet<DependencyDescriptor>>();
            foreach (var pair in (from x in _dependencyModel.DependenciesPerSubmodule
                                  where (((SubmoduleDescriptor)x.Key).Module).IsPartOfProject
                                  select x))
            {
                dependenciesPerSubmodule.Add((SubmoduleDescriptor)pair.Key, pair.Value);
            }
            foreach (var submodule in dependenciesPerSubmodule.Keys)
            {
                var moduleKey = "Module " + submodule.Module.Identifier;
                if (!discoveredNodes.ContainsKey(moduleKey))
                {
                    continue;
                }
                var moduleGraph = (SubGraph)discoveredNodes[moduleKey];
                var targetKey = "Submodule " + submodule.Identifier;
                if (!discoveredNodes.ContainsKey(targetKey))
                {
                    continue;
                }
                var target = discoveredNodes[targetKey];
                foreach (var dependency in dependenciesPerSubmodule[submodule])
                {
                    var sourceSubmodule = dependency.Source.ImplementingType.Submodule;
                    if (!Equals(sourceSubmodule.Module, submodule.Module)) continue;
                    var sourceKey = "Submodule " + sourceSubmodule.Identifier;
                    if (sourceKey == targetKey) continue;
                    if (!discoveredNodes.ContainsKey(sourceKey))
                    {
                        continue;
                    }
                    var source = discoveredNodes[sourceKey];
                    if (discoveredTransitions.ContainsKey(sourceKey + " -> " + targetKey))
                    {
                        discoveredTransitions[sourceKey + " -> " + targetKey].GenericInt++;
                        continue;
                    }
                    var transition = GraphBuilder.BuildTransition(source, target);
                    transition.Brush = new SolidColorBrush(Colors.DimGray);
                    transition.TransitionStyle = TransitionStyle.Bezier;
                    transition.GenericInt = 1;
                    transition.RoutingStyle = IsHorizontal ? RoutingStyle.FreeHandHorizontal : RoutingStyle.FreeHandVertical;
                    moduleGraph.Transitions.Add(transition);
                    discoveredTransitions.Add(sourceKey + " -> " + targetKey, transition);
                }
            }
        }

        private static void BuildModuleTransitions(Dictionary<ModuleDescriptor, ISet<DependencyDescriptor>> dependenciesPerAssembly, Dictionary<string, Node> discoveredNodes,
            Dictionary<string, Transition> discoveredTransitions, GraphParameter parameter)
        {
            foreach (var assembly in dependenciesPerAssembly.Keys)
            {
                var targetKey = "Module " + assembly.Identifier;
                if (!discoveredNodes.ContainsKey(targetKey))
                {
                    continue;
                }
                var target = discoveredNodes[targetKey];
                foreach (var dependency in dependenciesPerAssembly[assembly])
                {
                    var sourceKey = "Module " + dependency.Source.ImplementingType.Submodule.Module.Identifier;
                    if (sourceKey == targetKey) continue;
                    if (!discoveredNodes.ContainsKey(sourceKey))
                    {
                        continue;
                    }
                    var source = discoveredNodes[sourceKey];
                    if (discoveredTransitions.ContainsKey(sourceKey + " -> " + targetKey))
                    {
                        discoveredTransitions[sourceKey + " -> " + targetKey].GenericInt++;
                        continue;
                    }
                    var transition = GraphBuilder.BuildTransition(source, target);
                    transition.TransitionStyle = TransitionStyle.Bezier;
                    transition.RoutingStyle = parameter.HorizontalLayout ? RoutingStyle.FreeHandHorizontal : RoutingStyle.FreeHandVertical;
                    transition.GenericInt = 1;
                    parameter.Transitions.Add(transition);
                    discoveredTransitions.Add(sourceKey + " -> " + targetKey, transition);
                }
            }
        }

        private void BuildNodes(Dictionary<ModuleDescriptor, ISet<DependencyDescriptor>> dependenciesPerAssembly, bool limitToProject, Dictionary<string, Node> discoveredNodes,
            GraphParameter parameter)
        {
            //TODO if descriptor is in hidden nodes the ignore
            var hidden = new HashSet<IDescriptor>(HiddenNodes);

            var metrics = new MetricsImplementation(DependencyModel);
            foreach (ModuleDescriptor module in dependenciesPerAssembly.Keys)
            {
                if (limitToProject && !module.IsPartOfProject) continue;

                if (hidden.Contains(module))
                {
                    continue;
                }

                var node = GraphBuilder.BuildNode<SubGraph>(module);
                node.Text += " (" + metrics.NumberOfTypes(module) + ")";

                node.CollapsedHeight = node.Height;
                node.CollapsedWidth = node.Width;
                node.IsExpanded = false;
                node.Arrangement = new Layered(node, 20, 80, IsHorizontal)
                {
                    RemoveTransitiveEdges = HideTransitiveEdges
                };

                discoveredNodes.Add("Module " + module.Identifier, node);
                parameter.Nodes.Add(node);

                foreach (var submodule in module.Submodules)
                {
                    if (hidden.Contains(submodule))
                    {
                        continue;
                    }
                    var nodeSub = GraphBuilder.BuildNode<SubGraph>(submodule);
                    nodeSub.Text += " (" + metrics.NumberOfTypes(submodule) + ")";
                    nodeSub.CollapsedHeight = nodeSub.Height;
                    nodeSub.CollapsedWidth = nodeSub.Width;
                    nodeSub.IsExpanded = false;
                    nodeSub.Arrangement = new Layered(nodeSub, 20, 80, IsHorizontal && !CompactClassView)
                    {
                        RemoveTransitiveEdges = HideTransitiveEdges
                    };
                    discoveredNodes.Add("Submodule " + submodule.Identifier, nodeSub);
                    node.Nodes.Add(nodeSub);
                    foreach (var type in submodule.Types)
                    {
                        if (hidden.Contains(type))
                        {
                            continue;
                        }
                        var nodeType = GraphBuilder.BuildNode<SubGraph>(type);
                        nodeType.Text = type.Name;
                        nodeType.CollapsedHeight = nodeType.Height;
                        nodeType.CollapsedWidth = nodeType.Width;
                        nodeType.IsExpanded = false;
                        nodeType.Arrangement = new Layered(nodeType, CompactClassView ? 0 : 20, CompactClassView ? 40 : 80, IsHorizontal || CompactClassView)
                        {
                            RemoveTransitiveEdges = HideTransitiveEdges
                        };
                        discoveredNodes.Add("Type " + type.Identifier, nodeType);
                        nodeSub.Nodes.Add(nodeType);

                        foreach (var member1 in type.Members)
                        {
                            var member = GetDeclaringMember(member1, DependencyModel.DependenciesPerMember);
                            if (hidden.Contains(member))
                            {
                                continue;
                            }
                            var nodeMember = GraphBuilder.BuildNode<Node>(member);
                            if (CompactClassView)
                            {
                                GraphBuilder.ApplyCompactLayout(nodeMember);
                            }
                            nodeMember.Text = member.Signature;
                            if (discoveredNodes.ContainsKey("Member " + member.Identifier))
                            {
                                continue;
                            }
                            discoveredNodes.Add("Member " + member.Identifier, nodeMember);
                            nodeType.Nodes.Add(nodeMember);
                        }
                    }
                }
            }
        }
    }
}
